
// Copyright (C) 2011 Luca Piccioni
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//  
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//  
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Text;
using System.Globalization;
using System.Text;

using Derm.Raster;

namespace Derm.Render
{
	class FontGlyphImage
	{
		#region Constructors

		/// <summary>
		/// Static constructor.
		/// </summary>
		static FontGlyphImage()
		{
			CreateCharacterSets();
		}

		private FontGlyphImage(string family, uint size, FontStyle styles)
		{
			CreateFontGlyphImage(family, size, styles);
		}

		#endregion

		#region Glyph Coordinates

		/// <summary>
		/// Font glyph texture coordinate.
		/// </summary>
		public struct GlyphCoord
		{
			#region Glyph Geometry

			/// <summary>
			/// Glyph quad geometry width.
			/// </summary>
			public float Width;

			#endregion

			#region Glyph Texturing

			/// <summary>
			/// X coordinate offset (normalized texture coordinates).
			/// </summary>
			/// <remarks>
			/// 
			/// </remarks>
			public float TexCoordOffsetX;

			/// <summary>
			/// Y coordinate offset (normalized texture coordinates).
			/// </summary>
			public float TexCoordOffsetY;

			/// <summary>
			/// Glyph width (normalized texture coordinates).
			/// </summary>
			public float TexQuadWidth;

			/// <summary>
			/// Glyph height (normalized texture coordinates).
			/// </summary>
			public float TexQuadHeight;

			#endregion
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="c"></param>
		/// <returns></returns>
		public GlyphCoord GetGlyphCoord(Char c)
		{
			GlyphCoord coord;

			if (mGlyphCoords.TryGetValue(c, out coord) == false)
				coord = mGlyphCoords['?'];

			return (coord);
		}

		/// <summary>
		/// Glyph image coordinates.
		/// </summary>
		private Dictionary<Char, GlyphCoord> mGlyphCoords = new Dictionary<char,GlyphCoord>();

		#endregion

		#region Line Spacing

		/// <summary>
		/// Required line vertical space.
		/// </summary>
		public float LineSpace { get { return (mLineSpace); } }

		/// <summary>
		/// Required line vertical space.
		/// </summary>
		private float mLineSpace;

		#endregion

		#region Character Set

		private static void CreateCharacterSets()
		{
			StringBuilder sb = new StringBuilder(256);

			for (char c = (char)0; c < 256; c++) {
				// Do not include control characters
				if (Char.IsControl(c))
					continue;

				sb.Append(c);
			}

			sCharacterSet.Add(sb.ToString());
		}

		/// <summary>
		/// The character set used for rendering text strings.
		/// </summary>
		private static readonly List<string> sCharacterSet = new List<string>();

		#endregion

		#region Glyph Image Definition

		/// <summary>
		/// Image representing font glyphs.
		/// </summary>
		public Raster.Image GlyphImage { get { return (mGlyphImage); } }
		
		/// <summary>
		/// Create an image representing font glyphs.
		/// </summary>
		/// <param name="family"></param>
		/// <param name="size"></param>
		/// <param name="styles"></param>
		private void CreateFontGlyphImage(string family, uint size, FontStyle styles)
		{
			// Determine font style
			System.Drawing.FontStyle fontStyle = System.Drawing.FontStyle.Regular;

			if ((styles & FontStyle.Bold) != 0)
				fontStyle |= System.Drawing.FontStyle.Bold;
			if ((styles & FontStyle.Italic) != 0)
				fontStyle |= System.Drawing.FontStyle.Italic;

			// Create a Font, used for creating font texture
			Font font = new Font(family, (int)size, fontStyle, GraphicsUnit.Pixel);
			Bitmap fontBitmap = new Bitmap(1, 1);
			Graphics fontBitmapGraphics = Graphics.FromImage(fontBitmap);

			// Fixed line space
			mLineSpace = font.Height;
			// Compute glyph database
			mGlyphCoords = ComputeGlyphDatabase(fontBitmapGraphics, font, sCharacterSet[0]);
			// Get font texture size
			Size fontBitmapSize = ComputeBestGlyphImageSize(font, mGlyphCoords);

			fontBitmap = new Bitmap(fontBitmapSize.Width, fontBitmapSize.Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb);

			// Setup graphic context (antialiasing on transparent background)
			fontBitmapGraphics = Graphics.FromImage(fontBitmap);
			fontBitmapGraphics.SmoothingMode = SmoothingMode.AntiAlias;
			fontBitmapGraphics.TextRenderingHint = TextRenderingHint.AntiAlias;
			fontBitmapGraphics.Clear(Color.Transparent);

			// Solid white brush (font brush color)
			Brush fontBrush = new SolidBrush(Color.White);

			// Draw character set glyphs into font bitmap
			float x = 0.0f, y = 0.0f;

			foreach (char c in sCharacterSet[0]) {
				GlyphCoord glyphCoord = mGlyphCoords[c];

				// Carry to next line, if necessary
				if (x + glyphCoord.Width > fontBitmap.Width) {
					x = 0;
					y += font.Height;
				}

				// Draw character
				fontBitmapGraphics.DrawString(c.ToString(), font, fontBrush, x, y);

				// Register font glyph
				glyphCoord.TexCoordOffsetX = x / fontBitmapSize.Width;
				glyphCoord.TexCoordOffsetY = 1.0f - ((y + font.Height) / fontBitmapSize.Height);
				glyphCoord.TexQuadWidth = glyphCoord.Width / fontBitmapSize.Width;
				glyphCoord.TexQuadHeight = (float)font.Height / fontBitmapSize.Height;

				// Overwrite the previous
				mGlyphCoords[c] = glyphCoord;

				// Position of next character (ceil X coordinate)
				x += glyphCoord.Width;
				x = (float)Math.Ceiling(x);
			}

			// Create Raster.Image (for texture creation)
			ImageCodec.CodecCriteria fontImageCodecCriteria = new ImageCodec.CodecCriteria();

			mGlyphImage = NetImagingImageCodecPlugin.LoadFromBitmap(fontBitmap, fontImageCodecCriteria);

			// Save font image to disk
			ImageCodec.Instance.Save(CreateFontSignature(family, size, styles) + ".png", mGlyphImage, ImageFormat.Png);
		}

		private Dictionary<char, GlyphCoord> ComputeGlyphDatabase(Graphics graphics, Font font, string characterSet)
		{
			Dictionary<char, GlyphCoord> glyphDatabase = new Dictionary<char, GlyphCoord>();

			foreach (char c in characterSet) {
				SizeF glyphCoordSize = graphics.MeasureString(c.ToString(), font);

				GlyphCoord glyphCoord = new GlyphCoord();

				glyphCoord.Width = glyphCoordSize.Width;

				glyphCoord.TexCoordOffsetX = 0.0f;
				glyphCoord.TexCoordOffsetY = 0.0f;
				glyphCoord.TexQuadWidth = 0.0f;
				glyphCoord.TexQuadHeight = 0.0f;

				glyphDatabase[c] = glyphCoord;
			}

			return (glyphDatabase);
		}

		private Size ComputeBestGlyphImageSize(Font font, Dictionary<char, GlyphCoord> glyphDatabase)
		{
			uint size = 64;

			while (FitGlyphImageSize(font, glyphDatabase, size) == false)
				size *= 2;

			return (new Size((int)size, (int)size));
		}

		private bool FitGlyphImageSize(Font font, Dictionary<char, GlyphCoord> glyphDatabase, uint size)
		{
			int lineSpace = font.Height, lines = 0, x = 0;

			foreach (GlyphCoord glyphCoord in glyphDatabase.Values) {
				int charWidth = (int)Math.Ceiling(glyphCoord.Width);

				if (x + charWidth > size) {
					x = 0;
					lines++;

					if (lines * lineSpace > size)
						return (false);
				}

				x += charWidth;
			}

			return (true);
		}

		/// <summary>
		/// Image representing font glyphs.
		/// </summary>
		private Raster.Image mGlyphImage;

		#endregion

		#region FontGlyphImage Factory

		/// <summary>
		/// 
		/// </summary>
		/// <param name="family"></param>
		/// <param name="size"></param>
		/// <param name="styles"></param>
		/// <returns></returns>
		public static FontGlyphImage Create(string family, uint size, FontStyle styles)
		{
			FontGlyphImage fontGlyphImage;
			string fontSignature = CreateFontSignature(family, size, styles);

			if (sFontGlyphImages.TryGetValue(fontSignature, out fontGlyphImage) == false) {
				fontGlyphImage = new FontGlyphImage(family, size, styles);
				sFontGlyphImages.Add(fontSignature, fontGlyphImage);
			}

			return (fontGlyphImage);
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="family"></param>
		/// <param name="size"></param>
		/// <param name="styles"></param>
		/// <returns></returns>
		private static string CreateFontSignature(string family, uint size, FontStyle styles)
		{
			StringBuilder sb = new StringBuilder();

			sb.AppendFormat("{0}-{1}", family, size);
			if ((styles & FontStyle.Italic) != 0)
				sb.Append("-italic");
			if ((styles & FontStyle.Bold) != 0)
				sb.Append("-bold");

			return (sb.ToString());
		}

		/// <summary>
		/// Cached font glyph images.
		/// </summary>
		private static readonly Dictionary<string, FontGlyphImage> sFontGlyphImages = new Dictionary<string, FontGlyphImage>();

		#endregion
	}
}
